package com.moose.operator.web.filter;

import com.moose.operator.constant.AnonymousUrlConstant;
import com.moose.operator.constant.CacheNameConstant;
import com.moose.operator.constant.CommonConstant;
import com.moose.operator.constant.SecurityConstant;
import com.moose.operator.exception.BusinessException;
import com.moose.operator.model.api.ResultCode;
import com.moose.operator.util.IpUtils;
import com.moose.operator.util.RedisHelper;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.web.filter.OncePerRequestFilter;

/**
 * <p>
 * Description:
 * </p>
 *
 * @author taohua
 * @version v1.0.0
 * @date 2020-06-30 22:59:22:59
 * @see com.moose.operator.web.filter
 */
@Slf4j
public class MooseLoginVerifyLimitFilter extends OncePerRequestFilter {

  /**
   * 存放所有需要校验验证码的url
   */
  private final Map<String, String> urlMap = new HashMap<>(16);
  private final AuthenticationFailureHandler authenticationFailureHandler;

  private RedisHelper redisHelper;

  public MooseLoginVerifyLimitFilter(RedisHelper redisHelper,
      AuthenticationFailureHandler authenticationFailureHandler) {
    this.redisHelper = redisHelper;
    this.authenticationFailureHandler = authenticationFailureHandler;
  }

  /**
   * 初始化要拦截的url配置信息
   */
  @Override
  public void afterPropertiesSet() throws ServletException {
    super.afterPropertiesSet();
    addUrlToMap(AnonymousUrlConstant.LOGIN_IN_URL, CommonConstant.DEFAULT_PARAMETER_NAME_CODE_SMS);
  }

  /**
   * 如果不是登录请求，直接调用后面的过滤器链
   */
  @Override
  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
      FilterChain filterChain) throws ServletException, IOException {
    if (ObjectUtils.isNotEmpty(urlMap.get(request.getRequestURI())) &&
        StringUtils.equalsIgnoreCase(request.getMethod(), CommonConstant.HTTP_METHOD_POST)) {
      try {
        validate(request);
      } catch (BusinessException e) {
        authenticationFailureHandler.onAuthenticationFailure(request, response, e);
        return;
      }
    }
    filterChain.doFilter(request, response);
  }

  /**
   * 将系统中配置的需要校验验证码的URL根据校验的类型放入map
   *
   * @param urlString
   * @param smsParam
   */
  protected void addUrlToMap(String urlString, String smsParam) {
    if (StringUtils.isNotBlank(urlString)) {
      String[] urls = StringUtils.splitByWholeSeparatorPreserveAllTokens(urlString, ",");
      for (String url : urls) {
        urlMap.put(url, smsParam);
      }
    }
  }

  /**
   * TODO: 限制 - 手动输入验证码？ - 一段时间内不让登录
   *
   * @param request
   * @throws IOException
   */
  private void validate(HttpServletRequest request) throws IOException {

    StringBuilder sb = new StringBuilder(CacheNameConstant.LOGIN_LIMIT_KEY);

    String accountName = request.getParameter(SecurityConstant.DEFAULT_LOGIN_USERNAME_PARAMETER);
    if (StringUtils.isNotEmpty(accountName)) {
      sb.append(accountName);
    }

    String ip = IpUtils.getIpAddress();
    if (StringUtils.isNotEmpty(ip)) {
      sb.append(ip);
    }

    String phoneNumber = request.getParameter(CommonConstant.DEFAULT_PARAMETER_NAME_PHONE);
    if (StringUtils.isNotEmpty(phoneNumber)) {
      sb.append(phoneNumber);
    }

    // 计算登录次数
    String loginCountKey = sb.toString();
    Integer loginCount = (Integer) redisHelper.getValue(loginCountKey);
    if (ObjectUtils.isEmpty(loginCount)) {
      redisHelper.cacheValue(loginCountKey, 1, SecurityConstant.LOGIN_TIME_OF_SECONDS,
          TimeUnit.SECONDS);
    } else {
      // 判断手机号时间范围内，累计发送次数
      if (loginCount >= SecurityConstant.MAX_COUNT_OF_DAY) {
        throw new BusinessException(ResultCode.OPERATION_LIMIT_FAIL);
      }
      redisHelper.increment(loginCountKey, 1);
    }
  }
}
